Assumptions
Simulate non-linearity (quadratic)
set.seed(221020)
quadr <- function(x, a, b, c){ a + b*x + c*x^2 }
df <- tibble(
x = seq(-2, 2, length.out = 101),
y_good = abs(4 + (2 * x) + rnorm(101, 0, 1)),
y_quad = abs(quadr(x + 0.5, 1, 0, 1) + rnorm(101, 0, 1)),
)
p_good <- df %>%
ggplot(aes(x=x, y=y_good)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
NULL
p_quad <- df |>
ggplot(aes(x=x, y=y_quad)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
NULL
p_good + p_quad

check with component residual plots
component = the individual association of a particular predictor and
the outcome, with all the other predictors held constant.
residual = the differences between the line the model predicts and
the actual observed data points.
df
NA
NA
Simulate autocorrelated errors
If you roll a 6-sided die at 9am everyday, you’ll find there is no
correlation from one day to the next. Every time you roll the die you’ll
get a completely random number coming from the die. If you look at the
temperature at 9am everyday, you will notice that days tend to be
correlated with adjacent days. If it’s 10 degrees on Monday, it’s far
more likely to be 0-20 degrees on Tuesday than it is to be -20 degrees
or 50 degrees. Even though it might be quite common to have a 50 degree
morning, the previous data suggests it probably won’t be.
Probably needlessly complex, esp with so much other stuff to cover.
Worth mentioning conceptually but not belabouring.
# source: https://stackoverflow.com/a/77090029
acf.negbin <- function(N, mu, size, alpha, max.iter = 100, tol = 1e-5) {
m = length(alpha)
generate = function(){
x = sort(rnbinom(N,size=size,mu=mu))
y <- rnorm(length(x))
x[rank(stats::filter(y, alpha, circular = TRUE))]
}
a = generate()
iter <- 0L
ACF <- function(x) acf(x, lag.max = m - 1, plot = FALSE)$acf[1:m]
SSE <- function(x,alpha) sum((ACF(x) - alpha)^2)
while ((SSE0 <- SSE(a, alpha)) > tol) {
if ((iter <- iter + 1L) == max.iter) break
a1 <- generate()
if(SSE(a1,alpha) < SSE0) a <- a1
}
return(a)
}
set.seed(2023)
acf_hi = acf.negbin(
30, # how many obs to simulate
mu = 10, # mean of simulated data
size = 5, # dispersion parameter.
# the bigger, the tighter the data will cluster around the mean.
# alpha=c(1,0.5) # ? correl of points with lag = 1 and lag = 2
alpha=c(0.9, 0.8, 0.8, 0.8) # ? correl of points with lag = 1 and lag = 2
)
acf_hi
[1] 13 5 10 9 5 7 5 4 5 6 7 5 5 6 7 7 10 10 11 15 12 21 17 21 23 16
[27] 11 13 9 11
plot(acf_hi,type='l')

# mean(x)
acf(acf_hi)#acf[1:2]

set.seed(2023)
acf_lo <- sample(1:20, size = 30, replace = TRUE)
plot(acf_lo, type='l')

acf(acf_lo)

Simulate non-normal errors
Let’s try generating non-normal errors by sampling from an
asymmetrical beta distribution. Centre those estims on zero and then use
them as errors. So it’ll be normal in that the mean is zero, but they’ll
be super skewed.
set.seed(1)
beta_errors <- rbeta(101, shape1 = 1, shape2 = 50) |>
scale(scale = FALSE, center = TRUE) * 50
plot(beta_errors)

set.seed(221020)
df <- tibble(
x = seq(-2, 2, length.out = 101),
y_good = abs(4 + (2 * x) + rnorm(101, 0, 1)),
y_beta = abs(4 + (2 * x) + beta_errors),
# y_quad = abs(quadr(x + 0.5, 1, 0, 1) + rnorm(101, 0, 1)),
)
# df2
p_good <- df |>
ggplot(aes(x=x, y=y_good)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
labs(y = 'y') +
NULL
p_beta <- df |>
ggplot(aes(x=x, y=y_beta)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
labs(y = 'y') +
NULL
p_good + p_beta

Plot normality of errors
m_good <- lm(y_good ~ x, df)
m_beta <- lm(y_beta ~ x, df)
resid_df <- tibble(
good_resid = m_good$residuals,
beta_resid = m_beta$residuals,
)
sd_good <- sd(m_good$residuals)
sd_beta <- sd(m_beta$residuals)
p_good_resid <- resid_df |>
ggplot() +
geom_histogram(aes(x = good_resid)) +
geom_function(
fun = function(x) dnorm(x, mean = 0, sd = sd_good) * 12,
linewidth = 2) +
xlim(-1.5, 1.5) +
labs(
# title = 'normal residuals'
) +
NULL
p_beta_resid <- resid_df |>
ggplot() +
geom_histogram(aes(x = beta_resid)) +
geom_function(
fun = function(x) dnorm(x, mean = 0, sd = .95) * 35,
linewidth = 2
) +
xlim(-4.5, 4.5) +
labs(
# title = 'non-normal residuals'
) +
NULL
p_good_resid + p_beta_resid

q-q plot
plot(m_good, which = 2)

plot(m_beta, which = 2)

Simulate heteroscedasticity
Errors that increase with x
set.seed(221020)
df <- tibble(
x = seq(-2, 2, length.out = 101),
y_good = abs(4 + (2 * x) + rnorm(101, 0, 1)),
y_unequal = abs(4 + (2 * x) +
rnorm(101, mean = 0, sd = seq(0.1, 4, length.out = 101))
),
)
p_unequal <- df |>
ggplot(aes(x=x, y=y_unequal)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
NULL
p_good + p_unequal

m_unequal <- lm(y_unequal ~ x, data = df)
car::residualPlot(m_unequal) # same as plot() so idk if we need it

plot(m_good, which = 1)

plot(m_unequal, which = 1)

Diagnostics
Three checks for unusual data points
Make sure single points aren’t driving the results.
Outliers
Extreme values of y.
How quantify? Unexpectedly large (studentised) residuals.
Simulate
set.seed(221020)
df <- tibble(
x = seq(-2, 2, length.out = 101),
y_good = abs(4 + (2 * x) + rnorm(101, 0, 1)),
y_outl = y_good
)
df[95, 'y_outl'] <- 1.236227
# df[98, 'y_outl'] <- 2.623973
# df[30, 'y_outl'] <- 5.643033
# df[20, 'y_outl'] <- 3.160261
# df[14, 'y_outl'] <- 3.762352
p_outl <- df |>
ggplot(aes(x=x, y=y_outl)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
NULL
p_good + p_outl

Check with studentised residuals
unstandardised residuals -> standardised residuals ->
studentised residuals with rstudent(model)
m_outl <- lm(y_outl ~ x, data = df)
df$stud_resid <- rstudent(m_outl)
df |>
ggplot(aes(x = x, y = stud_resid)) +
geom_point() +
geom_hline(yintercept = 2, colour = 'red') +
geom_hline(yintercept = -2, colour = 'red') +
NULL

df |>
filter(stud_resid > 2 | stud_resid < -2)
High leverage
Simulate
set.seed(221020)
df <- tibble(
x = seq(-2, 2, length.out = 103),
y_good = abs(4 + (2 * x) + rnorm(103, 0, 1)),
y_levg = abs(4 + (2 * x) + rnorm(103, 0, 1)),
)
df <- rbind(
df,
tibble(
x = c(
3
# 4.5
# -3.5
),
y_good = NA,
y_levg = c(
6.068172
# 12.201827
# 2.2368030
)
)
)
p_levg <- df |>
ggplot(aes(x=x, y=y_levg)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
NULL
# p_good + p_levg
p_levg

Check with hat values
hat refers to the circumflex on top of a variable, which is like a
little way of saying “this is an estimate from a model”. it’s like in
natural language, like in French how a circumflex means “there used to
be an ‘s’ here”. just an extra annotation.
hatvalues() is our workhorse here.
# fit model
m_levg <- lm(y_levg ~ x, data = df)
# find mean expected hat value
n_predictors <- 1
n_observations <- nrow(df)
mean_hat <- (n_predictors+1)/n_observations
# points > 2*mean_hat will be considered high-leverage
df$hatvals <- hatvalues(m_levg)
df |>
ggplot(aes(x = x, y = hatvals)) +
geom_point() +
geom_hline(yintercept = 2*mean_hat, colour = 'red')

# Plot good hat values
tibble(
x = seq(-2, 2, length.out = 101),
hatvals = hatvalues(m_good)
) |>
ggplot(aes(x = x, y = hatvals)) +
geom_point() +
geom_hline(yintercept = 2*(2/101), colour = 'red')

The curve smiley face is because we’re looking at how far the values
are from the mean of the data when x = 0? The farther you get from the y
value when x=0, the more the hat values will increase?
The plot of the good data also goes to show that we shouldn’t
automatically be suspicious. Just if it’s really outside of the curve
that we’d expect. Discretion! Not just binary decisions.
High influence
Simulate
set.seed(221020)
df_infl <- tibble(
x = seq(-2, 2, length.out = 101),
y_good = abs(4 + (2 * x) + rnorm(101, 0, 1)),
y_infl = y_good,
)
# # Use the same outlier values
# df[95, 'y_infl'] <- 1.236227
# df[98, 'y_infl'] <- 2.623973
# df[30, 'y_infl'] <- 5.643033
# df[20, 'y_infl'] <- 3.160261
# df[14, 'y_infl'] <- 3.762352
# Add in the same high-leverage values
df_infl <- rbind(
df_infl,
tibble(
# x = 0, y_good = NA, y_infl = 12.20182
x = -1, y_good = NA, y_infl = 6.5
)
)
p_infl <- df_infl |>
ggplot(aes(x=x, y=y_infl)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
NULL
p_good + p_infl

Check with Cook’s distance
Cook’s distance is basically outlyingness * leverage. So it’s big if
either one of those are true, and very big if both are true.
Interpretation: the average distance the predicted outcome values
will move, if a given case is removed.
Possible thresholds:
- 1
- 4 / (sample_size - n_params - 1) = 4 / (n - k - 1)
- relative to all Ds in the data set
m_infl <- lm(y_infl ~ x, data = df_infl)
df_infl$D <- cooks.distance(m_infl)
n_obs <- nrow(df_infl)
n_pred <- 1
# infl
df_infl |>
ggplot(aes(x = x, y = D)) +
geom_point() +
geom_hline(yintercept = 4 / (n_obs - n_pred - 1), colour = 'red') +
NULL

# good
tibble(
x = seq(-2, 2, length.out = 101),
D = cooks.distance(m_good)
) |>
ggplot(aes(x = x, y = D)) +
geom_point() +
geom_hline(yintercept = 4 / (101 - n_pred - 1), colour = 'red')

plot(m_infl, which = 4)

plot(m_good, which = 4)

Check the outlier and leverage datasets too:
plot(m_outl, which = 4)

plot(m_levg, which = 4)

Check with covariance ratio
Covariance ratio indicates the influence of a data point on the
(co)variances of the regression parameters.
df$cov.r <- covratio(m_infl)
covr_upper <- 1 + ((3 * (n_predictors + 1))/n_observations)
covr_lower <- 1 - ((3 * (n_predictors + 1))/n_observations)
df |>
ggplot(aes(x = x, y = cov.r)) +
geom_point() +
geom_hline(yintercept = covr_upper, colour = 'red') +
geom_hline(yintercept = covr_lower, colour = 'red') +
NULL

tibble(
x = seq(-2, 2, length.out = 101),
cov.r = covratio(m_good)
) |>
ggplot(aes(x = x, y = cov.r)) +
geom_point() +
geom_hline(yintercept = covr_upper, colour = 'red') +
geom_hline(yintercept = covr_lower, colour = 'red') +
NULL

It will detect weirdly high-influence points if they’re there, but it
will also detect high-influence points regardless. Just because
something exceeds these thresholds DOES NOT MEAN IT’S WEIRD OR COMES
FROM A DIFF DISTRIBUTION/GENERATIVE PROCESS. This is why we need
discretion.
Check all these diagnostic measures at once with the function that
does it all
influence.measures(m_infl)
Influence measures of
lm(formula = y_infl ~ x, data = df) :
dfb.1_: difference between the predicted values for the
intercept with and without this observation
dfb.x: difference between the predicted values for the
slope with and without this observation <- there will be one of these
per predictor
dffit: difference between predicted values for the
outcome with and without this observation
cov.r: ratio of the covariance of regression parameters
with and without this observation
cook.d: Cook’s Distance of this observation
hat: hat value of this observation
Sensitivity analysis big change
set.seed(221020)
df_sens <- tibble(
x = seq(-2, 2, length.out = 101),
y_good = 0 + rnorm(101, 0, 2),
y_infl = y_good,
)
df_sens <- rbind(
df_sens,
tibble(
x = 4, y_good = NA, y_infl = 20
)
)
With data point:
df_infl |>
ggplot(aes(x = x, y = y_infl)) +
geom_point() +
geom_smooth(method = 'lm', se = F) +
labs(y = 'y')

summary(lm(y_infl ~ x, data = df_infl))
Call:
lm(formula = y_infl ~ x, data = df_infl)
Residuals:
Min 1Q Median 3Q Max
-5.2842 -1.3809 -0.2367 1.1195 17.8112
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.3952 0.2588 1.527 0.1299
x 0.4484 0.2111 2.124 0.0361 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 2.613 on 100 degrees of freedom
Multiple R-squared: 0.04318, Adjusted R-squared: 0.03361
F-statistic: 4.513 on 1 and 100 DF, p-value: 0.03611
Without data point:
df_infl |>
ggplot(aes(x = x, y = y_good)) +
geom_point() +
geom_smooth(method = 'lm', se = F)

summary(lm(y_good ~ x, data = df_infl))
Call:
lm(formula = y_good ~ x, data = df_infl)
Residuals:
Min 1Q Median 3Q Max
-4.8796 -1.1834 -0.0191 0.9650 4.7179
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.21887 0.18036 1.214 0.228
x -0.07027 0.15466 -0.454 0.651
Residual standard error: 1.813 on 99 degrees of freedom
(1 observation deleted due to missingness)
Multiple R-squared: 0.002081, Adjusted R-squared: -0.007999
F-statistic: 0.2064 on 1 and 99 DF, p-value: 0.6506
Finally: one check for relationships between predictors
Multicollinearity
Simulate
set.seed(1)
n_obs <- 101
corr_x1y <- 0.8
corr_x2y <- 0.6
# corr_x1x2 <- 0.9 # vif = 5.3
corr_x1x2 <- 0.93 # vif = TBD
corr_df <- MASS::mvrnorm(
n = n_obs,
mu = c(0, 0, 0),
Sigma = matrix(
c(1, corr_x1y, corr_x2y,
corr_x1y, 1, corr_x1x2,
corr_x2y, corr_x1x2, 1),
nrow = 3),
empirical = TRUE
)
colnames(corr_df) <- c('y', 'x1', 'x2')
corr_df <- as_tibble(corr_df)
pairs(corr_df)

corr_df |> as_tibble() |>
ggplot(aes(x = x1, y = x2)) +
geom_point()

set.seed(1)
uncorr_x1x2 <- 0.0 # ideally this quantity would be low
uncorr_df <- MASS::mvrnorm(
n = n_obs,
mu = c(0, 0, 0),
Sigma = matrix(
c(1, corr_x1y, corr_x2y,
corr_x1y, 1, uncorr_x1x2,
corr_x2y, uncorr_x1x2, 1),
nrow = 3),
empirical = TRUE
)
colnames(uncorr_df) <- c('y', 'x1', 'x2')
uncorr_df <- as_tibble(uncorr_df)
pairs(uncorr_df)

uncorr_df |>
ggplot(aes(x = x1, y = x2)) +
geom_point()

corr_df |>
ggplot(aes(x = x1, y = y, colour = x2)) +
geom_point()

uncorr_df |>
ggplot(aes(x = x1, y = y, colour = x2)) +
geom_point()

Fit models
m_corr <- lm(y ~ x1 + x2, data = corr_df)
summary(m_corr)
Call:
lm(formula = y ~ x1 + x2, data = corr_df)
Residuals:
Min 1Q Median 3Q Max
-1.17835 -0.32896 -0.00607 0.27669 1.17206
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1.831e-17 4.568e-02 0.000 1
x1 1.791e+00 1.249e-01 14.343 < 2e-16 ***
x2 -1.066e+00 1.249e-01 -8.534 1.81e-13 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.4591 on 98 degrees of freedom
Multiple R-squared: 0.7935, Adjusted R-squared: 0.7893
F-statistic: 188.3 on 2 and 98 DF, p-value: < 2.2e-16
m_uncorr <- lm(y ~ x1 + x2, data = uncorr_df)
summary(m_uncorr)
Call:
lm(formula = y ~ x1 + x2, data = uncorr_df)
Residuals:
Min 1Q Median 3Q Max
-1.050e-07 -2.903e-08 -9.250e-10 2.979e-08 1.266e-07
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1.117e-16 4.736e-09 0 1
x1 8.000e-01 4.760e-09 168067180 <2e-16 ***
x2 6.000e-01 4.760e-09 126050385 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 4.76e-08 on 98 degrees of freedom
Multiple R-squared: 1, Adjusted R-squared: 1
F-statistic: 2.207e+16 on 2 and 98 DF, p-value: < 2.2e-16
Check with VIF
vif(m_uncorr)
x1 x2
1 1
vif(m_corr)
x1 x2
7.401925 7.401925
<5 is low. between 5 and 10 is moderate. More than 10 is a big
problem.
LS0tCnRpdGxlOiAiTGVjdHVyZSAwOCBwbGF5Z3JvdW5kIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKLS0tCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoY2FyKQpsaWJyYXJ5KHBlcmZvcm1hbmNlKQpsaWJyYXJ5KHBhdGNod29yaykKYGBgCgoKCiMgQXNzdW1wdGlvbnMKCiMjIFNpbXVsYXRlIG5vbi1saW5lYXJpdHkgKHF1YWRyYXRpYykKCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRX0Kc2V0LnNlZWQoMjIxMDIwKQpxdWFkciA8LSBmdW5jdGlvbih4LCBhLCBiLCBjKXsgYSArIGIqeCArIGMqeF4yICB9CgpkZiA8LSB0aWJibGUoCiAgeCA9IHNlcSgtMiwgMiwgbGVuZ3RoLm91dCA9IDEwMSksCiAgeV9nb29kID0gYWJzKDQgKyAoMiAqIHgpICsgcm5vcm0oMTAxLCAwLCAxKSksCiAgeV9xdWFkID0gYWJzKHF1YWRyKHggKyAwLjUsIDEsIDAsIDEpICsgcm5vcm0oMTAxLCAwLCAxKSksCikKCnBfZ29vZCA8LSBkZiAlPiUKICBnZ3Bsb3QoYWVzKHg9eCwgeT15X2dvb2QpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBzZSA9IEZBTFNFKSArCiAgTlVMTAoKcF9xdWFkIDwtIGRmIHw+CiAgZ2dwbG90KGFlcyh4PXgsIHk9eV9xdWFkKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJywgc2UgPSBGQUxTRSkgKwogIE5VTEwKCnBfZ29vZCArIHBfcXVhZApgYGAKCiMjIyBjaGVjayB3aXRoIGNvbXBvbmVudCByZXNpZHVhbCBwbG90cwoKY29tcG9uZW50ID0gdGhlIGluZGl2aWR1YWwgYXNzb2NpYXRpb24gb2YgYSBwYXJ0aWN1bGFyIHByZWRpY3RvciBhbmQgdGhlIG91dGNvbWUsIHdpdGggYWxsIHRoZSBvdGhlciBwcmVkaWN0b3JzIGhlbGQgY29uc3RhbnQuCgpyZXNpZHVhbCA9IHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZSBsaW5lIHRoZSBtb2RlbCBwcmVkaWN0cyBhbmQgdGhlIGFjdHVhbCBvYnNlcnZlZCBkYXRhIHBvaW50cy4KCgpgYGB7cn0KZGYKCgpgYGAKCgoKCiMjIFNpbXVsYXRlIGF1dG9jb3JyZWxhdGVkIGVycm9ycwoKPiBJZiB5b3Ugcm9sbCBhIDYtc2lkZWQgZGllIGF0IDlhbSBldmVyeWRheSwgeW91J2xsIGZpbmQgdGhlcmUgaXMgbm8gY29ycmVsYXRpb24gZnJvbSBvbmUgZGF5IHRvIHRoZSBuZXh0LiBFdmVyeSB0aW1lIHlvdSByb2xsIHRoZSBkaWUgeW91J2xsIGdldCBhIGNvbXBsZXRlbHkgcmFuZG9tIG51bWJlciBjb21pbmcgZnJvbSB0aGUgZGllLgpJZiB5b3UgbG9vayBhdCB0aGUgdGVtcGVyYXR1cmUgYXQgOWFtIGV2ZXJ5ZGF5LCB5b3Ugd2lsbCBub3RpY2UgdGhhdCBkYXlzIHRlbmQgdG8gYmUgY29ycmVsYXRlZCB3aXRoIGFkamFjZW50IGRheXMuIElmIGl0J3MgMTAgZGVncmVlcyBvbiBNb25kYXksIGl0J3MgZmFyIG1vcmUgbGlrZWx5IHRvIGJlIDAtMjAgZGVncmVlcyBvbiBUdWVzZGF5IHRoYW4gaXQgaXMgdG8gYmUgLTIwIGRlZ3JlZXMgb3IgNTAgZGVncmVlcy4gRXZlbiB0aG91Z2ggaXQgbWlnaHQgYmUgcXVpdGUgY29tbW9uIHRvIGhhdmUgYSA1MCBkZWdyZWUgbW9ybmluZywgdGhlIHByZXZpb3VzIGRhdGEgc3VnZ2VzdHMgaXQgcHJvYmFibHkgd29uJ3QgYmUuCgpQcm9iYWJseSBuZWVkbGVzc2x5IGNvbXBsZXgsIGVzcCB3aXRoIHNvIG11Y2ggb3RoZXIgc3R1ZmYgdG8gY292ZXIuCldvcnRoIG1lbnRpb25pbmcgY29uY2VwdHVhbGx5IGJ1dCBub3QgYmVsYWJvdXJpbmcuCgoKYGBge3J9CiMgc291cmNlOiBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL2EvNzcwOTAwMjkgCgphY2YubmVnYmluIDwtIGZ1bmN0aW9uKE4sIG11LCBzaXplLCBhbHBoYSwgbWF4Lml0ZXIgPSAxMDAsIHRvbCA9IDFlLTUpIHsKICAKICBtID0gbGVuZ3RoKGFscGhhKQogIAogIGdlbmVyYXRlID0gZnVuY3Rpb24oKXsKICAgeCA9IHNvcnQocm5iaW5vbShOLHNpemU9c2l6ZSxtdT1tdSkpCiAgIHkgPC0gcm5vcm0obGVuZ3RoKHgpKQogICB4W3Jhbmsoc3RhdHM6OmZpbHRlcih5LCBhbHBoYSwgY2lyY3VsYXIgPSBUUlVFKSldCiAgfQogIAogIGEgPSBnZW5lcmF0ZSgpCiAgaXRlciA8LSAwTAogIAogIEFDRiA8LSBmdW5jdGlvbih4KSBhY2YoeCwgbGFnLm1heCA9IG0gLSAxLCBwbG90ID0gRkFMU0UpJGFjZlsxOm1dCiAgU1NFIDwtIGZ1bmN0aW9uKHgsYWxwaGEpIHN1bSgoQUNGKHgpIC0gYWxwaGEpXjIpCiAgICAKICB3aGlsZSAoKFNTRTAgPC0gU1NFKGEsIGFscGhhKSkgPiB0b2wpIHsKICAgIGlmICgoaXRlciA8LSBpdGVyICsgMUwpID09IG1heC5pdGVyKSBicmVhawogICAgYTEgPC0gZ2VuZXJhdGUoKQogICAgaWYoU1NFKGExLGFscGhhKSA8IFNTRTApIGEgPC0gYTEKICB9CiAgICAKICByZXR1cm4oYSkKCn0KYGBgCgpgYGB7cn0Kc2V0LnNlZWQoMjAyMykKYWNmX2hpID0gYWNmLm5lZ2JpbigKICAzMCwgICAgICAgICAgICAjIGhvdyBtYW55IG9icyB0byBzaW11bGF0ZQogIG11ID0gMTAsICAgICAgIyBtZWFuIG9mIHNpbXVsYXRlZCBkYXRhCiAgc2l6ZSA9IDUsICAgICAjIGRpc3BlcnNpb24gcGFyYW1ldGVyLiAKICAgICAgICAgICAgICAgICAgIyB0aGUgYmlnZ2VyLCB0aGUgdGlnaHRlciB0aGUgZGF0YSB3aWxsIGNsdXN0ZXIgYXJvdW5kIHRoZSBtZWFuLgogICMgYWxwaGE9YygxLDAuNSkgICMgPyBjb3JyZWwgb2YgcG9pbnRzIHdpdGggbGFnID0gMSBhbmQgbGFnID0gMiAKICBhbHBoYT1jKDAuOSwgMC44LCAwLjgsIDAuOCkgICMgPyBjb3JyZWwgb2YgcG9pbnRzIHdpdGggbGFnID0gMSBhbmQgbGFnID0gMiAKICApCgphY2ZfaGkKYGBgCgoKYGBge3J9CnBsb3QoYWNmX2hpLHR5cGU9J2wnKQojIG1lYW4oeCkKYWNmKGFjZl9oaSkjYWNmWzE6Ml0KYGBgCgpgYGB7cn0Kc2V0LnNlZWQoMjAyMykKYWNmX2xvIDwtIHNhbXBsZSgxOjIwLCBzaXplID0gMzAsIHJlcGxhY2UgPSBUUlVFKQoKcGxvdChhY2ZfbG8sIHR5cGU9J2wnKQphY2YoYWNmX2xvKQpgYGAKCgoKIyMgU2ltdWxhdGUgbm9uLW5vcm1hbCBlcnJvcnMKCkxldCdzIHRyeSBnZW5lcmF0aW5nIG5vbi1ub3JtYWwgZXJyb3JzIGJ5IHNhbXBsaW5nIGZyb20gYW4gYXN5bW1ldHJpY2FsIGJldGEgZGlzdHJpYnV0aW9uLgpDZW50cmUgdGhvc2UgZXN0aW1zIG9uIHplcm8gYW5kIHRoZW4gdXNlIHRoZW0gYXMgZXJyb3JzLgpTbyBpdCdsbCBiZSBub3JtYWwgaW4gdGhhdCB0aGUgbWVhbiBpcyB6ZXJvLCBidXQgdGhleSdsbCBiZSBzdXBlciBza2V3ZWQuCgpgYGB7cn0Kc2V0LnNlZWQoMSkKYmV0YV9lcnJvcnMgPC0gcmJldGEoMTAxLCBzaGFwZTEgPSAxLCBzaGFwZTIgPSA1MCkgfD4gCiAgc2NhbGUoc2NhbGUgPSBGQUxTRSwgY2VudGVyID0gVFJVRSkgKiA1MApwbG90KGJldGFfZXJyb3JzKQpgYGAKCgpgYGB7ciBtZXNzYWdlID0gRn0Kc2V0LnNlZWQoMjIxMDIwKQoKZGYgPC0gdGliYmxlKAogIHggPSBzZXEoLTIsIDIsIGxlbmd0aC5vdXQgPSAxMDEpLAogIHlfZ29vZCA9IGFicyg0ICsgKDIgKiB4KSArIHJub3JtKDEwMSwgMCwgMSkpLCAKICB5X2JldGEgPSBhYnMoNCArICgyICogeCkgKyBiZXRhX2Vycm9ycyksCiAgCiAgIyB5X3F1YWQgPSBhYnMocXVhZHIoeCArIDAuNSwgMSwgMCwgMSkgKyBybm9ybSgxMDEsIDAsIDEpKSwKKQojIGRmMgoKcF9nb29kIDwtIGRmIHw+CiAgZ2dwbG90KGFlcyh4PXgsIHk9eV9nb29kKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJywgc2UgPSBGQUxTRSkgKwogIGxhYnMoeSA9ICd5JykgKwogIE5VTEwKCnBfYmV0YSA8LSBkZiB8PgogIGdncGxvdChhZXMoeD14LCB5PXlfYmV0YSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScsIHNlID0gRkFMU0UpICsKICBsYWJzKHkgPSAneScpICsKICBOVUxMCgpwX2dvb2QgKyBwX2JldGEKYGBgCgoKIyMjIFBsb3Qgbm9ybWFsaXR5IG9mIGVycm9ycwoKYGBge3J9Cm1fZ29vZCA8LSBsbSh5X2dvb2QgfiB4LCBkZikKbV9iZXRhIDwtIGxtKHlfYmV0YSB+IHgsIGRmKQpgYGAKCmBgYHtyIG1lc3NhZ2UgPSBGfQpyZXNpZF9kZiA8LSB0aWJibGUoCiAgZ29vZF9yZXNpZCA9IG1fZ29vZCRyZXNpZHVhbHMsCiAgYmV0YV9yZXNpZCA9IG1fYmV0YSRyZXNpZHVhbHMsCikKCnNkX2dvb2QgPC0gc2QobV9nb29kJHJlc2lkdWFscykKc2RfYmV0YSA8LSBzZChtX2JldGEkcmVzaWR1YWxzKQoKcF9nb29kX3Jlc2lkIDwtIHJlc2lkX2RmIHw+CiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gZ29vZF9yZXNpZCkpICsKICBnZW9tX2Z1bmN0aW9uKAogICAgZnVuID0gZnVuY3Rpb24oeCkgZG5vcm0oeCwgbWVhbiA9IDAsIHNkID0gc2RfZ29vZCkgKiAxMiwKICAgIGxpbmV3aWR0aCA9IDIpICsKICB4bGltKC0xLjUsIDEuNSkgKwogIGxhYnMoCiAgICAjIHRpdGxlID0gJ25vcm1hbCByZXNpZHVhbHMnCiAgKSArCiAgTlVMTAoKcF9iZXRhX3Jlc2lkIDwtIHJlc2lkX2RmIHw+CiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gYmV0YV9yZXNpZCkpICsKICBnZW9tX2Z1bmN0aW9uKAogICAgZnVuID0gZnVuY3Rpb24oeCkgZG5vcm0oeCwgbWVhbiA9IDAsIHNkID0gLjk1KSAqIDM1LAogICAgbGluZXdpZHRoID0gMgogICkgKwogIHhsaW0oLTQuNSwgNC41KSArCiAgbGFicygKICAgICMgdGl0bGUgPSAnbm9uLW5vcm1hbCByZXNpZHVhbHMnCiAgKSArCiAgTlVMTAoKcF9nb29kX3Jlc2lkICsgcF9iZXRhX3Jlc2lkCmBgYAoKIyMjIHEtcSBwbG90CgpgYGB7cn0KcGxvdChtX2dvb2QsIHdoaWNoID0gMikKcGxvdChtX2JldGEsIHdoaWNoID0gMikKYGBgCgoKCgojIyBTaW11bGF0ZSBoZXRlcm9zY2VkYXN0aWNpdHkKCkVycm9ycyB0aGF0IGluY3JlYXNlIHdpdGggeAoKYGBge3IgbWVzc2FnZSA9IEZ9CnNldC5zZWVkKDIyMTAyMCkKCmRmIDwtIHRpYmJsZSgKICB4ID0gc2VxKC0yLCAyLCBsZW5ndGgub3V0ID0gMTAxKSwKICB5X2dvb2QgPSBhYnMoNCArICgyICogeCkgKyBybm9ybSgxMDEsIDAsIDEpKSwKICB5X3VuZXF1YWwgPSBhYnMoNCArICgyICogeCkgKyAKICAgICAgICAgICAgICAgICAgICBybm9ybSgxMDEsIG1lYW4gPSAwLCBzZCA9IHNlcSgwLjEsIDQsIGxlbmd0aC5vdXQgPSAxMDEpKQogICAgICAgICAgICAgICAgICApLAopCgpwX3VuZXF1YWwgPC0gZGYgfD4KICBnZ3Bsb3QoYWVzKHg9eCwgeT15X3VuZXF1YWwpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBzZSA9IEZBTFNFKSArCiAgTlVMTAoKcF9nb29kICsgcF91bmVxdWFsCmBgYAoKYGBge3J9Cm1fdW5lcXVhbCA8LSBsbSh5X3VuZXF1YWwgfiB4LCBkYXRhID0gZGYpCgpjYXI6OnJlc2lkdWFsUGxvdChtX3VuZXF1YWwpICAjIHNhbWUgYXMgcGxvdCgpIHNvIGlkayBpZiB3ZSBuZWVkIGl0CmBgYAoKYGBge3J9CnBsb3QobV9nb29kLCB3aGljaCA9IDEpCnBsb3QobV91bmVxdWFsLCB3aGljaCA9IDEpCmBgYAoKCiMgRGlhZ25vc3RpY3MKCiMjIFRocmVlIGNoZWNrcyBmb3IgdW51c3VhbCBkYXRhIHBvaW50cwoKTWFrZSBzdXJlIHNpbmdsZSBwb2ludHMgYXJlbid0IGRyaXZpbmcgdGhlIHJlc3VsdHMuCgojIyMgT3V0bGllcnMKCkV4dHJlbWUgdmFsdWVzIG9mIHkuCgpIb3cgcXVhbnRpZnk/ClVuZXhwZWN0ZWRseSBsYXJnZSAoc3R1ZGVudGlzZWQpIHJlc2lkdWFscy4KCiMjIyMgU2ltdWxhdGUKCmBgYHtyIG1lc3NhZ2UgPSBGfQpzZXQuc2VlZCgyMjEwMjApCmRmIDwtIHRpYmJsZSgKICB4ID0gc2VxKC0yLCAyLCBsZW5ndGgub3V0ID0gMTAxKSwKICB5X2dvb2QgPSBhYnMoNCArICgyICogeCkgKyBybm9ybSgxMDEsIDAsIDEpKSwKICB5X291dGwgPSB5X2dvb2QKKQoKZGZbOTUsICd5X291dGwnXSA8LSAxLjIzNjIyNwojIGRmWzk4LCAneV9vdXRsJ10gPC0gMi42MjM5NzMKIyBkZlszMCwgJ3lfb3V0bCddIDwtIDUuNjQzMDMzCiMgZGZbMjAsICd5X291dGwnXSA8LSAzLjE2MDI2MQojIGRmWzE0LCAneV9vdXRsJ10gPC0gMy43NjIzNTIKCnBfb3V0bCA8LSBkZiB8PgogIGdncGxvdChhZXMoeD14LCB5PXlfb3V0bCkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScsIHNlID0gRkFMU0UpICsKICBOVUxMCgpwX2dvb2QgKyBwX291dGwKYGBgCgoKCgojIyMjIENoZWNrIHdpdGggc3R1ZGVudGlzZWQgcmVzaWR1YWxzCgp1bnN0YW5kYXJkaXNlZCByZXNpZHVhbHMgLT4gIHN0YW5kYXJkaXNlZCByZXNpZHVhbHMgIC0+IHN0dWRlbnRpc2VkIHJlc2lkdWFscyB3aXRoIGByc3R1ZGVudChtb2RlbClgCgpgYGB7cn0KbV9vdXRsIDwtIGxtKHlfb3V0bCB+IHgsIGRhdGEgPSBkZikKCmRmJHN0dWRfcmVzaWQgPC0gcnN0dWRlbnQobV9vdXRsKQoKZGYgfD4KICBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0gc3R1ZF9yZXNpZCkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9ICAyLCBjb2xvdXIgPSAncmVkJykgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IC0yLCBjb2xvdXIgPSAncmVkJykgKwogIE5VTEwKCmRmIHw+CiAgZmlsdGVyKHN0dWRfcmVzaWQgPiAyIHwgc3R1ZF9yZXNpZCA8IC0yKQpgYGAKCgoKIyMjIEhpZ2ggbGV2ZXJhZ2UKCiMjIyMgU2ltdWxhdGUKCmBgYHtyIG1lc3NhZ2UgPSBGfQpzZXQuc2VlZCgyMjEwMjApCgpkZiA8LSB0aWJibGUoCiAgeCA9IHNlcSgtMiwgMiwgbGVuZ3RoLm91dCA9IDEwMyksCiAgeV9nb29kID0gYWJzKDQgKyAoMiAqIHgpICsgcm5vcm0oMTAzLCAwLCAxKSksCiAgeV9sZXZnID0gYWJzKDQgKyAoMiAqIHgpICsgcm5vcm0oMTAzLCAwLCAxKSksCikKCgpkZiA8LSByYmluZCgKICBkZiwKICB0aWJibGUoCiAgICB4ID0gYygKICAgICAgMwogICAgICAgICAgIyA0LjUKICAgICAgICAgICMgLTMuNQogICAgICAgICAgKSwKICAgIHlfZ29vZCA9IE5BLAogICAgeV9sZXZnID0gYygKICAgICAgNi4wNjgxNzIKICAgICAgIyAxMi4yMDE4MjcKICAgICAgIyAyLjIzNjgwMzAKICAgICAgKQogICkKKQogIApwX2xldmcgPC0gZGYgfD4KICBnZ3Bsb3QoYWVzKHg9eCwgeT15X2xldmcpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBzZSA9IEZBTFNFKSArCiAgTlVMTAoKIyBwX2dvb2QgKyBwX2xldmcKcF9sZXZnCmBgYAoKCiMjIyMgQ2hlY2sgd2l0aCBoYXQgdmFsdWVzCgpoYXQgcmVmZXJzIHRvIHRoZSBjaXJjdW1mbGV4IG9uIHRvcCBvZiBhIHZhcmlhYmxlLCB3aGljaCBpcyBsaWtlIGEgbGl0dGxlIHdheSBvZiBzYXlpbmcgInRoaXMgaXMgYW4gZXN0aW1hdGUgZnJvbSBhIG1vZGVsIi4KaXQncyBsaWtlIGluIG5hdHVyYWwgbGFuZ3VhZ2UsIGxpa2UgaW4gRnJlbmNoIGhvdyBhIGNpcmN1bWZsZXggbWVhbnMgInRoZXJlIHVzZWQgdG8gYmUgYW4gJ3MnIGhlcmUiLgpqdXN0IGFuIGV4dHJhIGFubm90YXRpb24uCgpgaGF0dmFsdWVzKClgIGlzIG91ciB3b3JraG9yc2UgaGVyZS4KCmBgYHtyfQojIGZpdCBtb2RlbAptX2xldmcgPC0gbG0oeV9sZXZnIH4geCwgZGF0YSA9IGRmKQoKIyBmaW5kIG1lYW4gZXhwZWN0ZWQgaGF0IHZhbHVlIApuX3ByZWRpY3RvcnMgPC0gMQpuX29ic2VydmF0aW9ucyA8LSBucm93KGRmKQptZWFuX2hhdCA8LSAobl9wcmVkaWN0b3JzKzEpL25fb2JzZXJ2YXRpb25zCgojIHBvaW50cyA+IDIqbWVhbl9oYXQgd2lsbCBiZSBjb25zaWRlcmVkIGhpZ2gtbGV2ZXJhZ2UKZGYkaGF0dmFscyA8LSBoYXR2YWx1ZXMobV9sZXZnKQpkZiB8PgogIGdncGxvdChhZXMoeCA9IHgsIHkgPSBoYXR2YWxzKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMiptZWFuX2hhdCwgY29sb3VyID0gJ3JlZCcpCgojIFBsb3QgZ29vZCBoYXQgdmFsdWVzCnRpYmJsZSgKICB4ID0gc2VxKC0yLCAyLCBsZW5ndGgub3V0ID0gMTAxKSwKICBoYXR2YWxzID0gaGF0dmFsdWVzKG1fZ29vZCkKKSB8PgogIGdncGxvdChhZXMoeCA9IHgsIHkgPSBoYXR2YWxzKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMiooMi8xMDEpLCBjb2xvdXIgPSAncmVkJykKYGBgCgpUaGUgY3VydmUgc21pbGV5IGZhY2UgaXMgYmVjYXVzZSB3ZSdyZSBsb29raW5nIGF0IGhvdyBmYXIgdGhlIHZhbHVlcyBhcmUgZnJvbSB0aGUgbWVhbiBvZiB0aGUgZGF0YSB3aGVuIHggPSAwPwpUaGUgZmFydGhlciB5b3UgZ2V0IGZyb20gdGhlIHkgdmFsdWUgd2hlbiB4PTAsIHRoZSBtb3JlIHRoZSBoYXQgdmFsdWVzIHdpbGwgaW5jcmVhc2U/CgpUaGUgcGxvdCBvZiB0aGUgZ29vZCBkYXRhIGFsc28gZ29lcyB0byBzaG93IHRoYXQgd2Ugc2hvdWxkbid0IGF1dG9tYXRpY2FsbHkgYmUgc3VzcGljaW91cy4KSnVzdCBpZiBpdCdzIHJlYWxseSBvdXRzaWRlIG9mIHRoZSBjdXJ2ZSB0aGF0IHdlJ2QgZXhwZWN0LgpEaXNjcmV0aW9uISAKTm90IGp1c3QgYmluYXJ5IGRlY2lzaW9ucy4KCgojIyMgSGlnaCBpbmZsdWVuY2UKCiMjIyMgU2ltdWxhdGUKCmBgYHtyIG1lc3NhZ2UgPSBGfQpzZXQuc2VlZCgyMjEwMjApCgpkZl9pbmZsIDwtIHRpYmJsZSgKICB4ID0gc2VxKC0yLCAyLCBsZW5ndGgub3V0ID0gMTAxKSwKICB5X2dvb2QgPSBhYnMoNCArICgyICogeCkgKyBybm9ybSgxMDEsIDAsIDEpKSwKICB5X2luZmwgPSB5X2dvb2QsCikKCmRmX2luZmwgPC0gcmJpbmQoCiAgZGZfaW5mbCwKICB0aWJibGUoCiAgICAjIHggPSAwLCB5X2dvb2QgPSBOQSwgeV9pbmZsID0gMTIuMjAxODIKICAgIHggPSAtMSwgeV9nb29kID0gTkEsIHlfaW5mbCA9IDYuNQogICkKKQoKcF9pbmZsIDwtIGRmX2luZmwgfD4KICBnZ3Bsb3QoYWVzKHg9eCwgeT15X2luZmwpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBzZSA9IEZBTFNFKSArCiAgTlVMTAoKcF9nb29kICsgcF9pbmZsCmBgYAoKCiMjIyMgQ2hlY2sgd2l0aCBDb29rJ3MgZGlzdGFuY2UKCkNvb2sncyBkaXN0YW5jZSBpcyBiYXNpY2FsbHkgb3V0bHlpbmduZXNzICogbGV2ZXJhZ2UuClNvIGl0J3MgYmlnIGlmIGVpdGhlciBvbmUgb2YgdGhvc2UgYXJlIHRydWUsIGFuZCB2ZXJ5IGJpZyBpZiBib3RoIGFyZSB0cnVlLgoKSW50ZXJwcmV0YXRpb246IHRoZSBhdmVyYWdlIGRpc3RhbmNlIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZSB2YWx1ZXMgd2lsbCBtb3ZlLCBpZiBhIGdpdmVuIGNhc2UgaXMgcmVtb3ZlZC4KClBvc3NpYmxlIHRocmVzaG9sZHM6CgotIDEKLSA0IC8gKHNhbXBsZV9zaXplIC0gbl9wYXJhbXMgLSAxKSA9IDQgLyAobiAtIGsgLSAxKQotIHJlbGF0aXZlIHRvIGFsbCBEcyBpbiB0aGUgZGF0YSBzZXQKCmBgYHtyfQptX2luZmwgPC0gbG0oeV9pbmZsIH4geCwgZGF0YSA9IGRmX2luZmwpCmRmX2luZmwkRCA8LSBjb29rcy5kaXN0YW5jZShtX2luZmwpCgpuX29icyA8LSBucm93KGRmX2luZmwpCm5fcHJlZCA8LSAxCgojIGluZmwKZGZfaW5mbCB8PgogIGdncGxvdChhZXMoeCA9IHgsIHkgPSBEKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNCAvIChuX29icyAtIG5fcHJlZCAtIDEpLCBjb2xvdXIgPSAncmVkJykgKwogIE5VTEwKYGBgCgoKYGBge3J9CiMgZ29vZAp0aWJibGUoCiAgeCA9IHNlcSgtMiwgMiwgbGVuZ3RoLm91dCA9IDEwMSksCiAgRCA9IGNvb2tzLmRpc3RhbmNlKG1fZ29vZCkKKSB8PgogIGdncGxvdChhZXMoeCA9IHgsIHkgPSBEKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNCAvICgxMDEgLSBuX3ByZWQgLSAxKSwgY29sb3VyID0gJ3JlZCcpCmBgYAoKYGBge3J9CnBsb3QobV9pbmZsLCB3aGljaCA9IDQpCmBgYAoKYGBge3J9CnBsb3QobV9nb29kLCB3aGljaCA9IDQpCmBgYAoKCgpDaGVjayB0aGUgb3V0bGllciBhbmQgbGV2ZXJhZ2UgZGF0YXNldHMgdG9vOgoKYGBge3J9CnBsb3QobV9vdXRsLCB3aGljaCA9IDQpCnBsb3QobV9sZXZnLCB3aGljaCA9IDQpCmBgYAoKIyMjIyBDaGVjayB3aXRoIGNvdmFyaWFuY2UgcmF0aW8KCkNvdmFyaWFuY2UgcmF0aW8gaW5kaWNhdGVzIHRoZSBpbmZsdWVuY2Ugb2YgYSBkYXRhIHBvaW50IG9uIHRoZSAoY28pdmFyaWFuY2VzIG9mIHRoZSByZWdyZXNzaW9uIHBhcmFtZXRlcnMuCgpgYGB7cn0KZGYkY292LnIgPC0gY292cmF0aW8obV9pbmZsKQoKY292cl91cHBlciA8LSAxICsgKCgzICogKG5fcHJlZGljdG9ycyArIDEpKS9uX29ic2VydmF0aW9ucykKY292cl9sb3dlciA8LSAxIC0gKCgzICogKG5fcHJlZGljdG9ycyArIDEpKS9uX29ic2VydmF0aW9ucykKCmRmIHw+CiAgZ2dwbG90KGFlcyh4ID0geCwgeSA9IGNvdi5yKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gY292cl91cHBlciwgY29sb3VyID0gJ3JlZCcpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBjb3ZyX2xvd2VyLCBjb2xvdXIgPSAncmVkJykgKwogIE5VTEwKCnRpYmJsZSgKICB4ID0gc2VxKC0yLCAyLCBsZW5ndGgub3V0ID0gMTAxKSwKICBjb3YuciA9IGNvdnJhdGlvKG1fZ29vZCkKKSB8PgogIGdncGxvdChhZXMoeCA9IHgsIHkgPSBjb3YucikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IGNvdnJfdXBwZXIsIGNvbG91ciA9ICdyZWQnKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gY292cl9sb3dlciwgY29sb3VyID0gJ3JlZCcpICsKICBOVUxMCmBgYAoKSXQgd2lsbCBkZXRlY3Qgd2VpcmRseSBoaWdoLWluZmx1ZW5jZSBwb2ludHMgaWYgdGhleSdyZSB0aGVyZSwgYnV0IGl0IHdpbGwgYWxzbyBkZXRlY3QgaGlnaC1pbmZsdWVuY2UgcG9pbnRzIHJlZ2FyZGxlc3MuCkp1c3QgYmVjYXVzZSBzb21ldGhpbmcgZXhjZWVkcyB0aGVzZSB0aHJlc2hvbGRzIERPRVMgTk9UIE1FQU4gSVQnUyBXRUlSRCBPUiBDT01FUyBGUk9NIEEgRElGRiBESVNUUklCVVRJT04vR0VORVJBVElWRSBQUk9DRVNTLgpUaGlzIGlzIHdoeSB3ZSBuZWVkIGRpc2NyZXRpb24uCgoKIyMjIENoZWNrIGFsbCB0aGVzZSBkaWFnbm9zdGljIG1lYXN1cmVzIGF0IG9uY2Ugd2l0aCB0aGUgZnVuY3Rpb24gdGhhdCBkb2VzIGl0IGFsbAoKYGBge3J9CmluZmx1ZW5jZS5tZWFzdXJlcyhtX2luZmwpCmBgYAoKLSBgZGZiLjFfYDogZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGZvciB0aGUgaW50ZXJjZXB0IHdpdGggYW5kIHdpdGhvdXQgdGhpcyBvYnNlcnZhdGlvbgotIGBkZmIueGA6IGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcHJlZGljdGVkIHZhbHVlcyBmb3IgdGhlIHNsb3BlIHdpdGggYW5kIHdpdGhvdXQgdGhpcyBvYnNlcnZhdGlvbiA8LSB0aGVyZSB3aWxsIGJlIG9uZSBvZiB0aGVzZSBwZXIgcHJlZGljdG9yCi0gYGRmZml0YDogZGlmZmVyZW5jZSBiZXR3ZWVuIHByZWRpY3RlZCB2YWx1ZXMgZm9yIHRoZSBvdXRjb21lIHdpdGggYW5kIHdpdGhvdXQgdGhpcyBvYnNlcnZhdGlvbgotIGBjb3YucmA6IHJhdGlvIG9mIHRoZSBjb3ZhcmlhbmNlIG9mIHJlZ3Jlc3Npb24gcGFyYW1ldGVycyB3aXRoIGFuZCB3aXRob3V0IHRoaXMgb2JzZXJ2YXRpb24KLSBgY29vay5kYDogQ29vaydzIERpc3RhbmNlIG9mIHRoaXMgb2JzZXJ2YXRpb24KLSBgaGF0YDogaGF0IHZhbHVlIG9mIHRoaXMgb2JzZXJ2YXRpb24KCgoKIyMgU2Vuc2l0aXZpdHkgYW5hbHlzaXMgYmlnIGNoYW5nZQoKYGBge3J9CnNldC5zZWVkKDIyMTAyMCkKCmRmX3NlbnMgPC0gdGliYmxlKAogIHggPSBzZXEoLTIsIDIsIGxlbmd0aC5vdXQgPSAxMDEpLAogIHlfZ29vZCA9IDAgKyBybm9ybSgxMDEsIDAsIDIpLAogIHlfaW5mbCA9IHlfZ29vZCwKKQoKZGZfc2VucyA8LSByYmluZCgKICBkZl9zZW5zLAogIHRpYmJsZSgKICAgIHggPSA0LCB5X2dvb2QgPSBOQSwgeV9pbmZsID0gMjAKICApCikKYGBgCgoKV2l0aCBkYXRhIHBvaW50OgoKYGBge3J9CmRmX3NlbnMgfD4KICBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geV9pbmZsKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJywgc2UgPSBGKSArCiAgbGFicyh5ID0gJ3knKQoKc3VtbWFyeShsbSh5X2luZmwgfiB4LCBkYXRhID0gZGZfc2VucykpCmBgYAoKV2l0aG91dCBkYXRhIHBvaW50OgoKYGBge3J9CmRmX3NlbnMgfD4KICBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geV9nb29kKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJywgc2UgPSBGKSArCiAgbGFicyh5ID0gJ3knKQoKc3VtbWFyeShsbSh5X2dvb2QgfiB4LCBkYXRhID0gZGZfc2VucykpCmBgYAoKCiMjIEZpbmFsbHk6IG9uZSBjaGVjayBmb3IgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHByZWRpY3RvcnMKCiMjIyBNdWx0aWNvbGxpbmVhcml0eQoKIyMjIyBTaW11bGF0ZQoKYGBge3J9CnNldC5zZWVkKDEpCgpuX29icyA8LSAxMDEKY29ycl94MXkgIDwtIDAuOApjb3JyX3gyeSAgPC0gMC42CiMgY29ycl94MXgyIDwtIDAuOSAgIyB2aWYgPSA1LjMKY29ycl94MXgyIDwtIDAuOTMgICAjIHZpZiA9IFRCRAoKY29ycl9kZiA8LSBNQVNTOjptdnJub3JtKAogIG4gPSBuX29icywKICBtdSA9IGMoMCwgMCwgMCksCiAgU2lnbWEgPSBtYXRyaXgoCiAgICBjKDEsIGNvcnJfeDF5LCBjb3JyX3gyeSwKICAgICBjb3JyX3gxeSwgMSwgY29ycl94MXgyLAogICAgIGNvcnJfeDJ5LCBjb3JyX3gxeDIsIDEpLAogICAgbnJvdyA9IDMpLAogIGVtcGlyaWNhbCA9IFRSVUUKICApCgpjb2xuYW1lcyhjb3JyX2RmKSA8LSBjKCd5JywgJ3gxJywgJ3gyJykKY29ycl9kZiA8LSBhc190aWJibGUoY29ycl9kZikKCnBhaXJzKGNvcnJfZGYpCgpjb3JyX2RmIHw+IGFzX3RpYmJsZSgpIHw+CiAgZ2dwbG90KGFlcyh4ID0geDEsIHkgPSB4MikpICsKICBnZW9tX3BvaW50KCkKYGBgCgpgYGB7cn0Kc2V0LnNlZWQoMSkKCnVuY29ycl94MXgyIDwtIDAuMCAgIyBpZGVhbGx5IHRoaXMgcXVhbnRpdHkgd291bGQgYmUgbG93Cgp1bmNvcnJfZGYgPC0gTUFTUzo6bXZybm9ybSgKICBuID0gbl9vYnMsCiAgbXUgPSBjKDAsIDAsIDApLAogIFNpZ21hID0gbWF0cml4KAogICAgYygxLCBjb3JyX3gxeSwgY29ycl94MnksCiAgICAgY29ycl94MXksIDEsIHVuY29ycl94MXgyLAogICAgIGNvcnJfeDJ5LCB1bmNvcnJfeDF4MiwgMSksCiAgICBucm93ID0gMyksCiAgZW1waXJpY2FsID0gVFJVRQogICkKCmNvbG5hbWVzKHVuY29ycl9kZikgPC0gYygneScsICd4MScsICd4MicpCnVuY29ycl9kZiA8LSBhc190aWJibGUodW5jb3JyX2RmKQoKcGFpcnModW5jb3JyX2RmKQoKdW5jb3JyX2RmIHw+IAogIGdncGxvdChhZXMoeCA9IHgxLCB5ID0geDIpKSArCiAgZ2VvbV9wb2ludCgpCmBgYAoKYGBge3J9CmNvcnJfZGYgfD4gCiAgZ2dwbG90KGFlcyh4ID0geDEsIHkgPSB5LCBjb2xvdXIgPSB4MikpICsKICBnZW9tX3BvaW50KCkKCnVuY29ycl9kZiB8PiAKICBnZ3Bsb3QoYWVzKHggPSB4MSwgeSA9IHksIGNvbG91ciA9IHgyKSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCgojIyMjIEZpdCBtb2RlbHMKCmBgYHtyfQptX2NvcnIgPC0gbG0oeSB+IHgxICsgeDIsIGRhdGEgPSBjb3JyX2RmKQpzdW1tYXJ5KG1fY29ycikKYGBgCgoKYGBge3J9Cm1fdW5jb3JyIDwtIGxtKHkgfiB4MSArIHgyLCBkYXRhID0gdW5jb3JyX2RmKQpzdW1tYXJ5KG1fdW5jb3JyKQpgYGAKCgoKIyMjIyBDaGVjayB3aXRoIFZJRgoKYGBge3J9CnZpZihtX3VuY29ycikKYGBgCgoKYGBge3J9CnZpZihtX2NvcnIpCmBgYAoKCjw1IGlzIGxvdy4KYmV0d2VlbiA1IGFuZCAxMCBpcyBtb2RlcmF0ZS4KTW9yZSB0aGFuIDEwIGlzIGEgYmlnIHByb2JsZW0uCgoKCgojIyBQbG90cyB3aXRoIGBwZXJmb3JtYW5jZTo6Y2hlY2tfbW9kZWwoKWAKClNvbWUgb2YgdGhlc2UgYXNzdW1wdGlvbnMvZGlhZ25vc3RpY3MgY2FuIGJlIGNoZWNrZWQgZW4gbWFzc2UgdXNpbmcgYHBlcmZvcm1hbmNlOjpjaGVja19tb2RlbChtb2RlbClgLgoKLSBwb3N0ZXJpb3IgcHJlZGljdGl2ZSBjaGVjayAoYmFzaWNhbGx5IHVzZXMgdGhlIG1vZGVsIHRvIGdlbmVyYXRlIG5ldyBmYWtlIGRhdGEgYW5kIGNvbXBhcmVzIHRoYXQgZGF0YSB0byB0aGUgb2JzZXJ2ZWQgZGF0YSkKLSBhc3N1bXB0aW9uOiBsaW5lYXJpdHkKLSBhc3N1bXB0aW9uOiBob21vZ2VuZWl0eSBvZiByZXNpZHVhbCB2YXJpYW5jZSBha2EgaG9tb3NjZWQKLSBkaWFnbm9zdGljczogaW5mbHVlbnRpYWwgb2JzZXJ2YXRpb25zIAotIGRpYWdub3N0aWNzOiBjb2xsaW5lYXJpdHkKLSBhc3N1bXB0aW9uOiBub3JtYWxpdHkgb2YgcmVzaWR1YWxzCgpgYGB7cn0KY2hlY2tfbW9kZWwobV9nb29kKQpgYGAKCmBgYHtyfQpjaGVja19tb2RlbChtX2JldGEpCmBgYAoKYGBge3J9CmNoZWNrX21vZGVsKG1fY29ycikKYGBgCgoKYGBge3J9CmNoZWNrX21vZGVsKG1faW5mbCkKYGBgCgpgYGB7cn0KY2hlY2tfbW9kZWwobV9sZXZnKQpgYGAKCmBgYHtyfQpjaGVja19tb2RlbChtX291dGwpCmBgYAoKYGBge3J9CmNoZWNrX21vZGVsKG1fdW5jb3JyKQpgYGAKCgpgYGB7cn0KY2hlY2tfbW9kZWwobV91bmVxdWFsKQpgYGA=